package com.dhy.commadstate.util;

import android.content.Context;
import android.os.Environment;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public abstract class AndroidBasicSerializable<DataModule extends Serializable> implements Serializable {
    @IgnoreSerializable
    protected String fileName;
    @IgnoreSerializable
    protected Context context;

    /**
     * application file folder.
     * <p/>use class name as fileName
     */
    public AndroidBasicSerializable(Context context) {
        this(context, null);
    }

    /**
     * @param fileName file name or AbsolutePath
     */
    public AndroidBasicSerializable(Context context, String fileName) {
        this.context = context;
        this.fileName = fileName != null ? fileName : getClass().getName();
    }

    /**
     * load object data from file.
     */
    public abstract DataModule load();

    protected DataModule load(DataModule t) {
        @SuppressWarnings("unchecked")
        Map<String, Object> f = (Map<String, Object>) new AndroidSerializableHelper(context, fileName).read();
        if (f != null && !f.isEmpty()) {
            load(f, t);
        }
        return t;
    }

    /**
     * ignore Context.class
     */
    public static void load(Map<String, Object> from, Object to) {
        Field[] fileds = to.getClass().getDeclaredFields();
        for (Field i : fileds) {
            try {
                if (from.containsKey(i.getName())) {
                    i.setAccessible(true);
                    i.set(to, from.get(i.getName()));
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * createOrUpdate to file.
     */
    public boolean save() {
        Field[] fileds = this.getClass().getDeclaredFields();
        Map<String, Object> map = new HashMap<String, Object>();
        for (Field i : fileds) {
            try {
                if (!i.isAnnotationPresent(IgnoreSerializable.class)) {
                    i.setAccessible(true);
                    map.put(i.getName(), i.get(this));
                }
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return new AndroidSerializableHelper(context, fileName).save((Serializable) map);
    }


    @Retention(RetentionPolicy.RUNTIME)
    public @interface IgnoreSerializable {
    }

    private class AndroidSerializableHelper extends SerializableHelper {
        Context context;

        public AndroidSerializableHelper(Context c, String fileName) {
            super(null);
            this.context = c;
            setFileName(fileName);
        }

        /**
         * application file folder.
         *
         * @param fileName file name or AbsolutePath
         */
        public SerializableHelper setFileName(String fileName) {
            try {
                String root = Environment.getExternalStorageDirectory().getAbsolutePath();
                if (!fileName.startsWith(root)) {
                    fileName = new File(context.getFilesDir(), fileName).getPath();
                }
            } catch (Exception ignored) {
            }
            setFilePath(fileName);
            return this;
        }
    }

    private class SerializableHelper {
        private String filePath;

        public SerializableHelper(String filePath) {
            this.filePath = filePath;
        }

        public String getFilePath() {
            return filePath;
        }

        public Serializable read() {
            File f = new File(filePath);
            if (!f.exists()) {
                try {
                    f.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return read(f);
        }

        public SerializableHelper setFilePath(String filePath) {
            this.filePath = filePath;
            return this;
        }

        public Serializable read(File file) {
            try {
                return read(new FileInputStream(file));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                throw new IllegalArgumentException("read serializableObject error!");
            }
        }

        public Serializable read(InputStream inputStream) {
            Serializable module = null;
            try {
                ObjectInput input = new ObjectInputStream(inputStream);
                try {
                    module = (Serializable) input.readObject();
                } finally {
                    input.close();
                }
            } catch (ClassNotFoundException ex) {
                ex.printStackTrace();
            } catch (IOException ex) {
//			ex.printStackTrace();
            }
            return module;
        }

        public Serializable read(byte[] data) {
            return read(new ByteArrayInputStream(data));
        }

        public boolean save(Serializable serializableObject) {
            return save(new File(filePath), serializableObject);
        }

        public boolean save(File file, Serializable serializableObject) {
            try {
                if (!file.exists()) {
                    file.createNewFile();
                }
                return save(new FileOutputStream(file), serializableObject);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                throw new IllegalArgumentException("createOrUpdate serializableObject error!");
            } catch (IOException e) {
                e.printStackTrace();
            }
            return false;
        }

        public boolean save(OutputStream outputStream, Serializable serializableObject) {
            try {
                ObjectOutput output = new ObjectOutputStream(outputStream);
                try {
                    output.writeObject(serializableObject);
                    return true;
                } finally {
                    output.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return false;
        }
    }
}
